/**
 * https://medium.com/bitcraft/so-you-want-to-build-an-ethereum-hd-wallet-cb2b7d7e4998
 * Requirements
bip39 (for mnemonic generation)
hdkey (implementation of BIP 32)
ethereumjs-util (henceforth ethUtil)
ethereumjs-tx (henceforth ethTx)
web3 (for communicating to an Ethereum node)
 */

var bip39 = require('bip39');
var fs = require('fs');
// var hdkey = require('ethereumjs-wallet/hdkey');//Utilities for handling Ethereum keys
var hdkey = require('hdkey');
var ethUtil = require('ethereumjs-util');//A collection of utility functions for Ethereum
const ethTx = require('ethereumjs-tx');//A simple module for creating, manipulating and signing ethereum transactions
const Web3 = require('web3');//web3.js is a collection of libraries which allow you to interact with a local or remote ethereum node, using a HTTP or IPC connection.
var keythereum = require('keythereum');//Create, import and export Ethereum keys


/**
 * create HD-Wallet
 */
let createHDWallet = (language) => {
    console.log('*********************************************start create HD-Wallet***********************************\n\n');
    (language === 'zh-CN') ? (language = bip39.wordlists.chinese_simplified) : (language = bip39.wordlists.english);
    const mnemonic = bip39.generateMnemonic(null, null, language); //generates string
    const seed = bip39.mnemonicToSeed(mnemonic); //creates seed buffer

    const root = hdkey.fromMasterSeed(seed);
    const masterPrivateKey = root.privateKey.toString('hex');

    const addrNode = root.derive("m/44'/60'/0'/0/0"); //line 1
    const pubKey = ethUtil.privateToPublic(addrNode._privateKey);
    const addr = ethUtil.publicToAddress(pubKey).toString('hex');
    const address = ethUtil.toChecksumAddress(addr);

    console.log('bip39:==>', { mnemonic: mnemonic, seed: seed, masterPrivateKey: masterPrivateKey, addrNode: addrNode, pubKey: pubKey, addr: addr, address });
    console.log('*********************************************end create HD-Wallet***********************************\n\n');
    /*
    If using ethereumjs-wallet instead do after line 1:
    const address = addrNode.getWallet().getChecksumAddressString();
    */
    return { address: address, privateKey: addrNode };
}
/**
 * 
 * @param {privateKey} addrNode_privateKey 
 * Constructing, Signing, and Broadcasting Transactions
 */
let signedTransaction = async (addrNode) => {
    console.log('*********************************************start sign transaction***********************************\n\n');
    /**
     * We have now constructed and bundled up our signed transaction. All that’s left is to broadcast it. There are a few ways to go about this:
    
    Via etherscan.io using their public eth_sendRawTransaction endpoint
    Via infura.io which will give you access to an ethereum node serverside
    Via a local geth node
     */

    // geth --testnet --ws
    const web3 = new Web3(
        new Web3.providers.HttpProvider('http://192.168.2.155:8546')//https://ropsten.infura.io/
    );

    //Verify connection is successful
    web3.eth.net.isListening()
        .then(() => console.log('is connected'))
        .catch(e => console.log('Wow. Something went wrong'));

    // let coinbase = await web3.eth.getCoinbase();
    // console.log('coinbase:', coinbase);
    // console.log('coinbase balance:', await web3.eth.getBalance(coinbase));
    const params = {
        nonce: 0,
        to: '0xCE988789eDAa5b660e8d2D6E2861BC84815649A3',
        value: 0.1,
        gasPrice: 5000000000,
        gasLimit: 21000,
        chainId: 3
    };

    const tx = new ethTx(params);
    //Signing the transaction with the correct private key
    tx.sign(addrNode._privateKey);
    const serializedTx = await tx.serialize();
    console.log('serializedTx:==>', serializedTx);
    console.log('*********************************************end sign transaction***********************************\n\n');


    console.log('*********************************************start sendSignTransaction***********************************\n\n');
    /**
     * And now we can finally broadcast our transaction which we constructed by hand across the Ethereum (testnet) Universe and return the transaction hash.
     */
    web3.eth.sendSignedTransaction(
        `0x${serializedTx.toString('hex')}`,
        (error, result) => {
            if (error) { console.log(`Error: ${error}`); }
            else { console.log(`Result: ${result}`); }
        }
    );
    console.log('*********************************************end sendSignTransaction***********************************\n\n');
    // return serializedTx;
}

let sendSignedTransaction = (serializedTx) => {
    web3.eth.sendSignedTransaction(
        `0x${serializedTx.toString('hex')}`,
        (error, result) => {
            if (error) { console.log(`Error: ${error}`); }
            else { console.log(`Result: ${result}`); }
        }
    );
}

/**
* Key creation
Generate a new random private key (256 bit), as well as the salt (256 bit) used by the key derivation function, 
and the initialization vector (128 bit) used to AES-128-CTR encrypt the key. create is asynchronous if it is passed a callback function, 
and synchronous otherwise.
*/
let createKey = () => {
    console.log('*********************************************start create thereum key***********************************');

    // optional private key and initialization vector sizes in bytes
    // (if params is not passed to create, keythereum.constants is used by default)
    var dk_params = { keyBytes: 32, ivBytes: 16 };

    // synchronous
    var dk_key = keythereum.create(dk_params);

    console.log('dk:==>', dk_key);
    console.log('*********************************************end create thereum key***********************************\n\n');
    return dk_key;
}
/**
 * Key export
You will need to specify a password and (optionally) a key derivation function. If unspecified, PBKDF2-SHA256 will be used to derive the AES secret key.
 * @param {dk_key} privateKey 
 */
let exportKey = (dk_key) => {
    var password = "wheethereum";
    // var kdf = "pbkdf2"; // or "scrypt" to use the scrypt kdf
    // Note: if options is unspecified, the values in keythereum.constants are used.
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    console.log('*********************************************start dump created thereum key***********************************\n\n');
    // synchronous
    var keyObject = keythereum.dump(password, dk_key.privateKey, dk_key.salt, dk_key.iv, options);
    console.log('keyObject:==>', keyObject);
    if (!fs.existsSync('./keystore')) {
        fs.mkdirSync('./keystore')
    }
    // keyObject:
    keythereum.exportToFile(keyObject); //export to a file

    return keyObject;

    console.log('*********************************************end dump created thereum key***********************************\n\n');

}
/**
 * Key import
Importing a key from geth's keystore can only be done on Node. The JSON file is parsed into an object with the same structure as keyObject above.
* @param {address} address 
*/
let importKey = (keyObject) => {
    var password = "wheethereum";
    console.log('*********************************************start import created thereum key***********************************\n\n');

    // Specify a data directory (optional; defaults to ~/.ethereum)
    var datadir = "E:\\martin\\projects\\hdwallet-vs\\hdwallet";

    // Synchronous
    var keyObject = keythereum.importFromFile(keyObject.address, datadir);

    console.log('*********************************************end import created thereum key***********************************\n\n');
    // to recover the plaintext private key from the key object, use keythereum.recover. The private key is returned as a Buffer.

    // // synchronous
    var privateKey = keythereum.recover(password, keyObject);

    console.log('to recover the plaintext private：===>', privateKey);

}

module.exports = { createKey, exportKey, importKey, createHDWallet, signedTransaction, sendSignedTransaction }